/**  
* Copyright (c) 2009, Noel Lynch All rights reserved. Redistribution and use  
* in source and binary forms, with or without modification, are permitted  
* provided that the following conditions are met: Redistributions of source  
* code must retain the above copyright notice, this list of conditions and the  
* following disclaimer. Redistributions in binary form must reproduce the above  
* copyright notice, this list of conditions and the following disclaimer in the  
* documentation and/or other materials provided with the distribution. Neither  
* the name of Noel Lynch nor the names of contributors may be used to  
* endorse or promote products derived from this software without specific prior  
* written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT  
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A  
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR  
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,  
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,  
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;  
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR  
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF  
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  
*/
// created by noellynch
// Aug 7, 2009

package com.sceneworker.editors.texturepainter;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.concurrent.Callable;

import org.lwjgl.opengl.GL11;

import com.jme.image.Texture;
import com.jme.util.GameTaskQueueManager;
import com.jme.util.geom.BufferUtils;
import com.jme2utils.objects.ITextureAndImageCallbacks;
import com.jme2utils.staticutils.TextureUtils;
import com.myjavautils.math.MathUtils;
import com.myjavautils.swing.SimpleAlert;

public class TextureBufferedImageRep {

	private		BufferedImage			mcl_javaRGB;
	private		BufferedImage			mcl_javaAlpha;
	private		FloatBuffer				mcl_textureData;
	
	private		Texture					mcl_texture;
	
	private static IntBuffer	mcl_idBuffer = BufferUtils.createIntBuffer(16);
	
	private	final	static		int		TEXTURE_DATA_FORMAT = GL11.GL_RGBA;
	private	final	static		int		JAVA_IMAGE_FORMAT = BufferedImage.TYPE_INT_ARGB;
	
	private		ITextureAndImageCallbacks			mcl_listener;
	
	private		boolean			mb_textureDataUpdateComplete = true;
	
	private		Rectangle		mcl_bufferBlitRect = new Rectangle();
	private		Rectangle		mcl_intensityBlitRect = new Rectangle();
	
	public	enum	MixType {
		add, subtract, multiply, replace;
	}
	
	public	enum	ImageMode {
		rgba, alpha;
	}
	
	public	TextureBufferedImageRep(Texture cl_texture, int i_w, int i_h) {
		this(cl_texture, i_w, i_h, false);
	}
	
	public	TextureBufferedImageRep(Texture cl_texture, int i_w, int i_h, boolean b_inGLThread) {
		mcl_texture = cl_texture;
		
		allocateJavaImage(i_w, i_h);
		if(!allocateTextureDataBuffer(i_w, i_h)) {
			return;
		}
		
		if(!b_inGLThread) {
			GameTaskQueueManager.getManager().update(new Callable<Void>() {
				public Void call() throws Exception {
					//loadTextureIntoPixelArray();
					TextureUtils.loadTextureIntoPixelArray(mcl_texture, mcl_javaRGB, mcl_javaAlpha, mcl_textureData, mcl_listener);
					return null;
				}
			});
		} else {
			//loadTextureIntoPixelArray();
			TextureUtils.loadTextureIntoPixelArray(mcl_texture, mcl_javaRGB, mcl_javaAlpha, mcl_textureData, mcl_listener);
		}
		
	}
	
	public	void	addCallbackListener(ITextureAndImageCallbacks cl_listener) {
		mcl_listener = cl_listener;
	}
	
	final	public	BufferedImage		getJavaRep(ImageMode cl_mode) {
		if(cl_mode == ImageMode.rgba) {
			return mcl_javaRGB;
		}
		
		return mcl_javaAlpha;
	}
	
	final	public	Texture		getTexture() {
		return mcl_texture;
	}
	
	final	private		void	allocateJavaImage(int i_w, int i_h) {
		mcl_javaRGB = new BufferedImage(i_w, i_h, JAVA_IMAGE_FORMAT);
		mcl_javaAlpha = new BufferedImage(i_w, i_h, JAVA_IMAGE_FORMAT);
	}
	
	final	private		boolean	allocateTextureDataBuffer(int i_w, int i_h) {
		try {
			mcl_textureData = BufferUtils.createFloatBuffer(i_w * i_h * 4);
		} catch(Exception cl_e) {
			SimpleAlert.doAlert("An exception occurred allocating texture data " + cl_e.getMessage(), null);
			return false;
		}
		
		return true;
	}
	
	private	void	initialiseBlitRects(int i_x, int i_y, int i_w, int i_h) {
		if(i_x < 0) {
			mcl_bufferBlitRect.x = 0;
			mcl_intensityBlitRect.x = -i_x;
			mcl_bufferBlitRect.width = i_w - mcl_intensityBlitRect.x;
			mcl_intensityBlitRect.width = mcl_bufferBlitRect.width;
		} else if((i_x + i_w) > mcl_texture.getImage().getWidth()) {
			mcl_bufferBlitRect.x = i_x;
			mcl_intensityBlitRect.x = 0;
			mcl_bufferBlitRect.width = mcl_texture.getImage().getWidth() - i_x;
			mcl_intensityBlitRect.width = mcl_bufferBlitRect.width;
		} else {
			mcl_bufferBlitRect.x = i_x;
			mcl_intensityBlitRect.x = 0;
			mcl_bufferBlitRect.width = i_w;
			mcl_intensityBlitRect.width = mcl_bufferBlitRect.width;
		}
		
		if(i_y < 0) {
			mcl_bufferBlitRect.y = 0;
			mcl_intensityBlitRect.y = -i_y;
			mcl_bufferBlitRect.height = i_h - mcl_intensityBlitRect.y;
			mcl_intensityBlitRect.height = mcl_bufferBlitRect.height;
		} else if((i_y + i_h) > mcl_texture.getImage().getHeight()) {
			mcl_bufferBlitRect.y = i_y;
			mcl_intensityBlitRect.y = 0;
			mcl_bufferBlitRect.height = mcl_texture.getImage().getHeight() - i_y;
			mcl_intensityBlitRect.height = mcl_bufferBlitRect.height;
		} else {
			mcl_bufferBlitRect.y = i_y;
			mcl_intensityBlitRect.y = 0;
			mcl_bufferBlitRect.height = i_h;
			mcl_intensityBlitRect.height = mcl_bufferBlitRect.height;
		}
		
		//System.out.println("buffer blit rect =  "+ mcl_bufferBlitRect.toString());
		//System.out.println("intensity blit rect =  "+ mcl_intensityBlitRect.toString());
		
	}
	
	/**
	 * 
	 * @param i_x
	 * @param i_y
	 * @param i_w
	 * @param i_h
	 * @param ia_values
	 */
	public	void	mixAlpha(int i_x, int i_y, float ia_values[], int i_vDim, MixType cl_mixType) {
		if(!mb_textureDataUpdateComplete) { return; }
		
		if(i_x >= mcl_texture.getImage().getWidth()) {
			return;
		}
		
		if(i_y >= mcl_texture.getImage().getHeight()) {
			return;
		}
		
		//System.out.println("painting with " + cl_mixType);
		initialiseBlitRects(i_x, i_y, i_vDim, i_vDim);
		
		// offset = width * bytesPerComp
		int		li_bufferRowByteLength = mcl_texture.getImage().getWidth() * 4;
		int		li_bufferOffsetStartX = (li_bufferRowByteLength * mcl_bufferBlitRect.y) + (mcl_bufferBlitRect.x * 4);
		int		li_bufferOffset;
		int		li_y = 0;
		int		li_vStartOffset = (mcl_intensityBlitRect.y * i_vDim) + mcl_intensityBlitRect.x;
		int		li_vOffset;
		
		mcl_textureData.rewind();
		
		float	lf_intensity;
		float	lf_oldV;
		
		for(; li_y < mcl_bufferBlitRect.height; li_y++, li_bufferOffsetStartX += li_bufferRowByteLength) {
			for(int li_x = 0; li_x < mcl_bufferBlitRect.width; li_x++) {
				li_bufferOffset = li_bufferOffsetStartX + (li_x * 4);
				li_vOffset = li_vStartOffset + ((li_y * i_vDim) + li_x);
				
				lf_oldV = mcl_textureData.get(li_bufferOffset + 3);
				lf_intensity = ia_values[li_vOffset];
				
				switch(cl_mixType) {
				case	add:
					lf_intensity += lf_oldV;
					break;
					
				case	subtract:
					lf_intensity = lf_oldV - lf_intensity;
					break;
					
				case	multiply:
					lf_intensity *= lf_oldV;
					break;
				
				case	replace:
					break;
				}
				
				lf_intensity = MathUtils.limit(lf_intensity, 0f, 1.0f);
				
				mcl_textureData.put(li_bufferOffset + 3, lf_intensity);
			}
		}
		
		GameTaskQueueManager.getManager().update(new Callable<Void>() {
			public Void call() throws Exception {
				updateTextureAfterMix(0, 0, mcl_javaRGB.getWidth(), mcl_javaRGB.getHeight());
				return null;
			}
		});
	}
	
	public	void	mixRGB(int i_x, int i_y, float ia_values[], int i_vDim, MixType cl_mixType, float f_r, float f_g, float f_b) {
		if(!mb_textureDataUpdateComplete) { return; }
		
		if(i_x >= mcl_texture.getImage().getWidth()) {
			return;
		}
		
		if(i_y >= mcl_texture.getImage().getHeight()) {
			return;
		}
		
		//System.out.println("painting with " + cl_mixType);
		initialiseBlitRects(i_x, i_y, i_vDim, i_vDim);
		
		// offset = width * bytesPerComp
		int		li_bufferRowByteLength = mcl_texture.getImage().getWidth() * 4;
		int		li_bufferOffsetStartX = (li_bufferRowByteLength * mcl_bufferBlitRect.y) + (mcl_bufferBlitRect.x * 4);
		int		li_bufferOffset;
		int		li_y = 0;
		int		li_vStartOffset = (mcl_intensityBlitRect.y * i_vDim) + mcl_intensityBlitRect.x;
		int		li_vOffset;
		
		mcl_textureData.rewind();
		
		float	lf_intensity;
		float	lf_newR = 0;
		float	lf_newG = 0;
		float	lf_newB = 0;
		float	lf_oldR = 0;
		float	lf_oldG = 0;
		float	lf_oldB = 0;
		
		for(; li_y < mcl_bufferBlitRect.height; li_y++, li_bufferOffsetStartX += li_bufferRowByteLength) {
			for(int li_x = 0; li_x < mcl_bufferBlitRect.width; li_x++) {
				li_bufferOffset = li_bufferOffsetStartX + (li_x * 4);
				li_vOffset = li_vStartOffset + ((li_y * i_vDim) + li_x);
				
				lf_oldR = mcl_textureData.get(li_bufferOffset);
				lf_oldG = mcl_textureData.get(li_bufferOffset + 1);
				lf_oldB = mcl_textureData.get(li_bufferOffset + 2);
				
				lf_intensity = ia_values[li_vOffset];
				
				switch(cl_mixType) {
				case	add:
					lf_newR = MathUtils.linearInterpolation(lf_intensity, lf_oldR, f_r);
					lf_newG = MathUtils.linearInterpolation(lf_intensity, lf_oldG, f_g);
					lf_newB = MathUtils.linearInterpolation(lf_intensity, lf_oldB, f_b);
					break;
					
				case	subtract:
					lf_newR = MathUtils.linearInterpolation(lf_intensity, lf_oldR, f_r);
					lf_newG = MathUtils.linearInterpolation(lf_intensity, lf_oldG, f_g);
					lf_newB = MathUtils.linearInterpolation(lf_intensity, lf_oldB, f_b);
					break;
					
				case	multiply:
					
					break;
				
				case	replace:
					lf_newR = f_r;
					lf_newG = f_g;
					lf_newB = f_b;
					break;
				}
				
				lf_newR = MathUtils.limit(lf_newR, 0f, 1f);
				lf_newG = MathUtils.limit(lf_newG, 0f, 1f);
				lf_newB = MathUtils.limit(lf_newB, 0f, 1f);
				
				mcl_textureData.put(li_bufferOffset, lf_newR);
				mcl_textureData.put(li_bufferOffset + 1, lf_newG);
				mcl_textureData.put(li_bufferOffset + 2, lf_newB);
			}
		}
		
		GameTaskQueueManager.getManager().update(new Callable<Void>() {
			public Void call() throws Exception {
				updateTextureAfterMix(0, 0, mcl_javaRGB.getWidth(), mcl_javaRGB.getHeight());
				return null;
			}
		});
	}
	
	private	void	updateTextureAfterMix(int i_x, int i_y, int i_w, int i_h) {
		mb_textureDataUpdateComplete = false;
		
		// update the texture with the new float buffer
		TextureUtils.updateTextureData(mcl_texture, mcl_textureData, i_x, i_y, i_w, i_h, mcl_listener);
		// update the java image representation of the image
		TextureUtils.populateJavaImages(mcl_javaRGB, mcl_javaAlpha, mcl_textureData, mcl_texture.getImage(), mcl_bufferBlitRect.x, mcl_bufferBlitRect.y, mcl_bufferBlitRect.width, mcl_bufferBlitRect.height);
		
		mb_textureDataUpdateComplete = true;
	}
	
	public	Point	toImageSpace(Point cl_componentPt, float f_componentWidth, float f_componentHeight) {
		Point		lcl_local = new Point();
		lcl_local.x = (int)((float)mcl_javaRGB.getWidth() * ((float)cl_componentPt.x / f_componentWidth));
		lcl_local.y = (int)((float)mcl_javaRGB.getHeight() * ((f_componentHeight - (float)cl_componentPt.y) / f_componentHeight));
		return lcl_local;
	}	
}
